/* This script file handles the game logic */

var maxColumn// = root.size;
var maxRow// = root.size;
var maxIndex// = maxColumn*maxRow;
var board// = new Array(maxIndex);
var blockSrc = "Ball.qml";
var scoresURL = "";
var gameDuration;
var component = Qt.createComponent(blockSrc);
var gameLevel = 1
var ballNum = gameLevel + 3
var selectedBall = null
var ballWidth// = background.cellWidth
var ballHeight// = background.cellHeight
var nextBallColorArray
var nextBallNum;
var gaming = false;

//how many same ball at one line can be deleted
var sameBallNum = 5

//Index function used instead of a 2D array
function index(column,row) {
    return column + (row * maxColumn);
}

function column(index) {
    return index%maxColumn
}

function row(index) {
    return Math.floor(index/maxColumn);
}

function columnFromX(x) {
    return Math.floor(x/ballWidth)
}

function rowFromY(y) {
    return Math.floor(y/ballHeight)
}

function xFromIndex(index) {
    return column(index)*ballWidth
}

function yFromIndex(index) {
    return row(index)*ballHeight
}

function indexFromXY(x, y) {
    return index(columnFromX(x), rowFromY(y))
}

function timeStr(msecs) {
    var secs = Math.floor(msecs/1000);
    var m = Math.floor(secs/60);
    var ret = "" + m + "m " + (secs%60) + "s";
    return ret;
}

function initGlobalVars() {
    //Calculate board size
    maxColumn = root.size
    maxRow = root.size
    maxIndex = maxRow*maxColumn;
    ballWidth = background.cellWidth
    ballHeight = background.cellHeight
    nextBallNum = root.nextBallNum
}

function startNewGame()
{
    gaming = true
    //Delete blocks from previous game
    if (board != null) {
        for (var i = 0; i<board.length; i++) {
            if (board[i] != null) {
                board[i].destroy();
                board[i] = null
            }
        }
    }
    if (movingBall != null) {
        movingBall.destroy()
        movingBall = null
    }

    selectedBall = null

    //Calculate board size
    initGlobalVars()

    //Close dialogs
    //    nameInputDialog.forceClose();
    //    dialog.forceClose();

    //Initialize Board
    board = new Array(maxIndex);
    root.score = 0;
    for (var column=0; column<maxColumn; column++){
        for (var row=0; row<maxRow; row++){
            board[index(column,row)] = null;
        }
    }
    //create random start block
    createRandomColorBall(root.startBallNum)

    createNextBallColorArray(nextBallNum)

    gameDuration = new Date();
}

function createNextBallColorArray(number) {
    nextBallColorArray = createRandomColorArray(number)
    for (var i = 0; i < nextBallColorArray.length; i++) {
        var ball = nextBallRepeater.itemAt(i)
        if (ball != null) {
            ball.type = nextBallColorArray[i]
        }
    }
}

function createBallFromNextColorArray() {
    return createRandomBlock(nextBallColorArray);
}

function createRandomColorBall(number) {
    var colorArray = createRandomColorArray(number)
    createRandomBlock(colorArray);
}

function createRandomColorArray(number) {
    var colorArray = new Array(number)
    for (var i = 0; i < number; i++) {
        colorArray[i] = Math.floor(Math.random() * root.colorNum)
    }
    return colorArray
}


/**
 * Create random blocks, the color is defined by colorArray
 * @param colorArray Block color array
 * @return true success, false full
 */
function createRandomBlock(colorArray) {
    for (var i = 0; i < colorArray.length; i++) {
        var emptyArray = emptyBoardArray()
        if (emptyArray.length === 0) {
            console.log("No empty cell")
            return false
        }

        var randomIndex = emptyArray[Math.floor(Math.random() * emptyArray.length)]
        var createdIndex = createBlock(column(randomIndex), row(randomIndex), colorArray[i])
        if (createdIndex !== -1 && createdIndex === randomIndex) {
            var score = sameBallDeleting(createdIndex)
            if (score > 0) {
                root.score += score
            }
        }
    }
    return true
}

function emptyBoardArray() {
    var list = new Array()
    var j = 0
    for (var i = 0; i < board.length; i++) {
        if (board[i] == null) {
            list[j++] = i
        }
    }
    return list
}

var fillFound;//Set after a floodFill call to the number of blocks found
var floodBoard;//Set to 1 if the floodFill reaches off that node

//moving ball's path
var ballMovingPath = new Array()
var ballMovingTargetIndex
//moving ball
var movingBall = null

function handleBoardClick(x,y)
{
    var column = columnFromX(x)
    var row = rowFromY(y)
    if(column >= maxColumn || column < 0 || row >= maxRow || row < 0)
        return;
    //clicked cell
    if (board[index(column, row)] == null) {
        //ball selected
        if (selectedBall != null) {
            //check path available
            var map = new Array(board.length)
            for (var i = 0; i < board.length; i++) {
                if (board[i] == null) {
                    map[i] = 0
                }
                else {
                    map[i] = 1
                }
            }

            ballMovingPath = engine.findPath(map, maxRow, maxColumn,
                                             rowFromY(selectedBall.y),columnFromX(selectedBall.x),
                                             row, column)
            if (ballMovingPath.length === 0) {
                //no path found, do nothing
                console.log("no path found, do nothing")
            }
            else {
                //moving the ball
                movingBall = selectedBall
                board[indexFromXY(selectedBall.x, selectedBall.y)] = null
                selectedBall.selected = false
                selectedBall = null
                ballMovingTargetIndex = ballMovingPath[ballMovingPath.length-1]
                var nextMove = ballMovingPath.shift()
                movingBall.nextX = xFromIndex(nextMove)
                movingBall.nextY = yFromIndex(nextMove)
                movingBall.moving = true
                console.log("movingBall, nextX", movingBall.nextX, "nextY", movingBall.nextY)
            }
        }
    }
    else {
        selectedBall = board[index(column, row)]
    }
}

function floodFill(column,row,type)
{
    if(board[index(column, row)] == null)
        return;
    var first = false;
    if(type == -1){
        first = true;
        type = board[index(column,row)].type;

        //Flood fill initialization
        fillFound = 0;
        floodBoard = new Array(maxIndex);
    }
    if(column >= maxColumn || column < 0 || row >= maxRow || row < 0)
        return;
    if(floodBoard[index(column, row)] == 1 || (!first && type != board[index(column,row)].type))
        return;
    floodBoard[index(column, row)] = 1;
    floodFill(column+1,row,type);
    floodFill(column-1,row,type);
    floodFill(column,row+1,type);
    floodFill(column,row-1,type);
    if(first==true && fillFound == 0)
        return;//Can't remove single blocks
    board[index(column,row)].dying = true;
    board[index(column,row)] = null;
    fillFound += 1;
}

function shuffleDown()
{
    //Fall down
    for(var column=0; column<maxColumn; column++){
        var fallDist = 0;
        for(var row=maxRow-1; row>=0; row--){
            if(board[index(column,row)] == null){
                fallDist += 1;
            }else{
                if(fallDist > 0){
                    var obj = board[index(column,row)];
                    obj.y = (row+fallDist) * background.cellWidth;
                    board[index(column,row+fallDist)] = obj;
                    board[index(column,row)] = null;
                }
            }
        }
    }
    //Fall to the left
    fallDist = 0;
    for(column=0; column<maxColumn; column++){
        if(board[index(column, maxRow - 1)] == null){
            fallDist += 1;
        }else{
            if(fallDist > 0){
                for(row=0; row<maxRow; row++){
                    obj = board[index(column,row)];
                    if(obj == null)
                        continue;
                    obj.x = (column-fallDist) * background.cellWidth;
                    board[index(column-fallDist,row)] = obj;
                    board[index(column,row)] = null;
                }
            }
        }
    }
}

function victoryCheck()
{
    //awards bonuses for no blocks left
    var deservesBonus = true;
    for(var column=maxColumn-1; column>=0; column--)
        if(board[index(column, maxRow - 1)] != null)
            deservesBonus = false;
    if(deservesBonus)
        background.score += 500;
    //Checks for game over
    if(deservesBonus || !(floodMoveCheck(0,maxRow-1, -1))){
        gameDuration = new Date() - gameDuration;
        nameInputDialog.show("You won! Please enter your name:                 ");
        nameInputDialog.initialWidth = nameInputDialog.text.width + 20;
        if(nameInputDialog.name == "")
            nameInputDialog.width = nameInputDialog.initialWidth;
        nameInputDialog.text.opacity = 0;//Just a spacer
    }
}

//only floods up and right, to see if it can find adjacent same-typed blocks
function floodMoveCheck(column, row, type)
{
    if(column >= maxColumn || column < 0 || row >= maxRow || row < 0)
        return false;
    if(board[index(column, row)] == null)
        return false;
    var myType = board[index(column, row)].type;
    if(type == myType)
        return true;
    return floodMoveCheck(column + 1, row, myType) ||
            floodMoveCheck(column, row - 1, board[index(column,row)].type);
}

function sameBallRange(ballArray, sameColorNum, colorType) {
    //range[0] = start, range[1] = -1 if either is -1, means no match
    var range = new Array(2)
    range[0] = -1
    range[1] = -1
    if (sameColorNum > ballArray.length) {
        console.log("ERROR::sameColorNum", sameColorNum,
                    "is larger than ballArray length", ballArray.length)
        return range
    }

    for (var i=0; i<ballArray.length; i++) {
        if (ballArray[i] == null) {
            if (range[0] !== -1 &&
                    range[1] !== -1 &&
                    (range[1] - range[0] + 1) >= sameColorNum) {
                console.log("Ball is null but reach the desired number")
                break;
            }
            range[0] = -1
            range[1] = -1
            continue
        }

        if (ballArray[i].type === colorType) {
            console.log("Index", i, "has the same color")
            //same color
            if (range[0] === -1) {
                range[0] = i
            }
            range[1] = i
        }
        else {
            //not the same color
            //found max enough same ball
            if (range[0] !== -1 &&
                    range[1] !== -1 &&
                    (range[1] - range[0] + 1) >= sameColorNum) {
                console.log("Color not the same but reach the desired number")
                break;
            }

            //            //rest balls are not enough, ignore
            //            if (i >= x) {
            //                console.log("Color not the same and left ball does not have enough number")
            //                range[0] = -1
            //                range[1] = -1
            //                break;
            //            }

            console.log("Color not the same but has enough left balls")
            range[0] = -1
            range[1] = -1
        }
    }
    if (range[1] - range[0] + 1 < sameColorNum) {
        range[0] = -1;
        range[1] = -1
    }

    console.log("return range[0]", range[0], "range[1]", range[1])
    return range
}

function sameBallDeleting(ballIndex) {
    if (board[ballIndex] == null) {
        console.log("No ball at index", ballIndex)
        return 0;
    }

    var sameBallArray = new Array()
    var ballRow = row(ballIndex)
    var ballColumn = column(ballIndex)
    var sameBallCount = 0
    var foundLineCount = 0
    var range

    console.log("ballRow",ballRow,"ballColumn",ballColumn,"sameBallNum",sameBallNum )

    //x axis checking
    var xStart = Math.max(0, ballColumn-sameBallNum+1)
    var xEnd = Math.min(ballColumn+sameBallNum-1, maxColumn - 1)
    console.log("xStart", xStart, "xEnd", xEnd)
    var xBallArray = new Array(xEnd - xStart)
    for (var i = xStart; i <= xEnd; i++) {
        xBallArray[i] = board[index(i, ballRow)]
    }

    range = sameBallRange(xBallArray, sameBallNum, board[ballIndex].type)
    if (range[0] != -1 && range[1] != -1 &&
            (range[1] - range[0] + 1 >= sameBallNum)) {
        for (i = range[0]; i <= range[1]; i++) {
            sameBallArray.push(xBallArray[i])
        }
        foundLineCount++
    }

    //Y axis checking
    var yStart = Math.max(0, ballRow-sameBallNum+1)
    var yEnd = Math.min(ballRow+sameBallNum-1, maxRow - 1)
    console.log("yStart", yStart, "yEnd", yEnd)
    var yBallArray = new Array(yEnd - yStart)
    for (i = yStart; i <= yEnd; i++) {
        yBallArray[i] = board[index(ballColumn, i)]
    }

    range = sameBallRange(yBallArray, sameBallNum, board[ballIndex].type)
    if (range[0] != -1 && range[1] != -1 &&
            (range[1] - range[0] + 1 >= sameBallNum)) {
        for (i = range[0]; i <= range[1]; i++) {
            if ((foundLineCount === 0) || yBallArray[i] != board[ballIndex]) {
                sameBallArray.push(yBallArray[i])
            }
        }
        foundLineCount++
    }

    // \ (slash) checking
    //The max value that row column could minus
    var topLeftMax = Math.min(Math.min(ballRow, ballColumn), sameBallNum-1)
    //The max value that row column could add
    var rightBottomMax = Math.min(Math.min(maxRow-ballRow-1, maxColumn-ballColumn-1), sameBallNum-1)
    console.log("topLeftMax", topLeftMax, "rightBottomMax", rightBottomMax)
    //Only calulate when have enough balls
    if (topLeftMax+rightBottomMax+1 >= sameBallNum) {
        //Total ball number needs to add self ball
        var slashBallArray = new Array(rightBottomMax+topLeftMax+1)
        for (i = -topLeftMax; i <= rightBottomMax; i++) {
            slashBallArray[i+topLeftMax] = board[index(ballColumn+i, ballRow+i)]
        }

        range = sameBallRange(slashBallArray, sameBallNum, board[ballIndex].type)
        if (range[0] != -1 && range[1] != -1 &&
                (range[1] - range[0] + 1 >= sameBallNum)) {
            for (i = range[0]; i <= range[1]; i++) {
                //Only add current ball when not added before
                if ((foundLineCount === 0) || slashBallArray[i] != board[ballIndex]) {
                    sameBallArray.push(slashBallArray[i])
                }
            }
            foundLineCount++
        }
    }

    // / (backslash) checking
    //The max value that row could minus, colum could add
    var rightTopMax = Math.min(Math.min(ballRow, maxColumn-ballColumn-1), sameBallNum-1)
    //The max value that row could add, column could minus
    var leftBottomMax = Math.min(Math.min(maxRow-ballRow-1, ballColumn), sameBallNum-1)
    console.log("rightTopMax", rightTopMax, "leftBottomMax", leftBottomMax)
    //Only calulate when have enough balls
    if (rightTopMax+leftBottomMax+1 >= sameBallNum) {
        //Total ball number needs to add self ball
        var backSlashBallArray = new Array(rightTopMax+leftBottomMax+1)
        for (i = 0; i <= rightTopMax + leftBottomMax; i++) {
            backSlashBallArray[i] = board[index(ballColumn+rightTopMax-i, ballRow-rightTopMax+i)]
        }

        range = sameBallRange(backSlashBallArray, sameBallNum, board[ballIndex].type)
        if (range[0] != -1 && range[1] != -1 &&
                (range[1] - range[0] + 1>= sameBallNum)) {
            for (i = range[0]; i <= range[1]; i++) {
                //Only add current ball when not added before
                if ((foundLineCount === 0)
                        || backSlashBallArray[i] != board[ballIndex]) {
                    sameBallArray.push(backSlashBallArray[i])
                }
            }
            foundLineCount++
        }
    }

    for (i =0; i <sameBallArray.length; i++) {
        board[indexFromXY(sameBallArray[i].x,sameBallArray[i].y)] = null
        sameBallArray[i].dying = true
//        sameBallArray[i].destroy()
    }
    return sameBallArray.length*foundLineCount*2
}

function openSaveHighScoreDialog() {
    saveScoreDialog.open()
}

function handleBallMovingFinished() {
    if (movingBall == null) {
        console.log("ERROR::movingBall is null")
    }
    else if (ballMovingPath.length === 0){
        console.log("no more move, checking ball deleting",
                    rowFromY(movingBall.y),
                    columnFromX(movingBall.x),
                    indexFromXY(movingBall.x, movingBall.y))
        board[ballMovingTargetIndex] = movingBall
        movingBall.moving = false
        movingBall = null
        updateBallPosition(ballMovingTargetIndex)
        //checking ball deleting
        var deleteBallScore = sameBallDeleting(ballMovingTargetIndex)
        if (deleteBallScore <= 0) {
            console.log("no ball deleted, create balls")
            if (createBallFromNextColorArray() === false || boardFull() === true) {
                console.log("create ball failed, board if full, GAME OVER!")
                if (root.score > root.highScore) {
                    console.log("save high score")
                    openSaveHighScoreDialog()
                }
                else {
                    console.log("not high score do nothing")
                }
            }
            else {
                createNextBallColorArray(nextBallNum)
            }
        }
        else {
            console.log("deleteBallScore", deleteBallScore)
            root.score += deleteBallScore
        }
    }
    else {
        var nextMoveIndex =  ballMovingPath.shift()
        movingBall.moving = false
        movingBall.nextX = xFromIndex(nextMoveIndex)
        movingBall.nextY = yFromIndex(nextMoveIndex)
        movingBall.moving = true
        console.log("moving to next step",
                    rowFromY(movingBall.y),
                    columnFromX(movingBall.x))
    }
}

function handleBallClicked(x, y) {
    var ballRow = rowFromY(y)
    var ballColumn = columnFromX(x)
    var ballIndex = index(ballColumn, ballRow)
    console.log("handleBallClicked, x,y", x,y, " row", ballRow, "column", ballColumn, "index", ballIndex)
    if (selectedBall != null) {
        //current clicked is the selected one, do nothing
        if (selectedBall == board[ballIndex]) {
            console.log("current clicked is the selected one, do nothing")
        }
        else {
            //
            console.log("clear selected flag",
                        "row", rowFromY(selectedBall.y),
                        "column", columnFromX(selectedBall.x))
            selectedBall.selected = false
        }
    }

    selectedBall = board[ballIndex]
    selectedBall.selected = true
}

function boardFull() {
    for (var i = 0; i <board.length; i++) {
        if (board[i] == null) {
            return false
        }
    }
    return true
}

function createBlock(column, row, colorType){
    // Note that we don't wait for the component to become ready. This will
    // only work if the block QML is a local file. Otherwise the component will
    // not be ready immediately. There is a statusChanged signal on the
    // component you could use if you want to wait to load remote files.
    if (component.status == Component.Ready){
        var dynamicObject = component.createObject(background,
                                                   {"type": colorType,
                                                       "x": column*background.cellWidth,
                                                       "y": row*background.cellHeight,
                                                       "width": background.cellWidth,
                                                       "height": background.cellHeight});
        if (dynamicObject == null){
            console.log("error creating block");
            console.log(component.errorString());
            return -1;
        }
        //dynamicObject.y = row*background.cellWidth;
        dynamicObject.spawned = true;
        dynamicObject.clicked.connect(handleBallClicked)
        dynamicObject.movingFinished.connect(handleBallMovingFinished)
        console.log("created ball, column", column, "row", row, "index", index(column,row))
        board[index(column,row)] = dynamicObject;
        return index(column,row);
    }else{
        console.log("error loading block component");
        console.log(component.errorString());
        return -1;
    }
}

function updateBallPosition(index) {
    if (board[index] != null && board[index].moving === false) {
        board[index].x = column(index)*ballWidth
        board[index].y = row(index)*ballHeight
        board[index].width = ballWidth
        board[index].height = ballHeight
    }
}

function updateAllBallPosition() {
    initGlobalVars()
    if (board != null) {
        for(var i = 0; i < board.length; i++) {
            //Ball is available and not moving
            updateBallPosition(i)
        }
    }
}

function openDB() {
    return openDatabaseSync("ColorLinzeScores", "1.0", "Local ColorLinze High Scores",100);
}

function updateHighScore() {
    var db = openDB()
    db.transaction(
                function(tx) {
                    tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(name TEXT, score NUMBER)');
                    var rs = tx.executeSql('SELECT * FROM Scores ORDER BY score desc LIMIT 1');
                    //Has score
                    if (rs.rows.length === 1) {
                        root.highScoreName = rs.rows.item(0).name
                        root.highScore = rs.rows.item(0).score
                    }
                }
                )
}

function showHighScoresDialog() {
    updateHighScoresDialog()
    highScoresDialog.open()
}

function updateHighScoresDialog() {
    highScoresDialog.model.clear()
    var db = openDB()
    db.transaction(
                function(tx) {
                    tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(name TEXT, score NUMBER)');
                    var rs = tx.executeSql('SELECT * FROM Scores ORDER BY score desc LIMIT 10');
                    console.log("Get high score length", rs.rows.length)
                    for(var i = 0; i < rs.rows.length; i++){
                        console.log("name",rs.rows.item(i).name, "score", rs.rows.item(i).score)
                        highScoresDialog.model.append({"name": rs.rows.item(i).name, "score":rs.rows.item(i).score})
                    }
                }
                )
}


function saveHighScore(name) {
    //OfflineStorage
    var db = openDB()
    var dataStr = "INSERT INTO Scores VALUES(?, ?)";
    var data = [name, root.score];
    db.transaction(
                function(tx) {
                    tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(name TEXT, score NUMBER)');
                    tx.executeSql(dataStr, data);
                }
                );
}

function isGaming() {
    if (board == null) {
        return false
    }
    else if (boardFull()) {
        return false
    }
    return gaming
}


function handleVisibleChanged(visible) {
    //To optimize the battery usage, when app is not visible, do not do the animation
    if (visible === false && selectedBall != null) {
        console.log("visible is false, disable the animation")
        selectedBall.selected = false
    }
    else if (visible === true && selectedBall != null) {
        //Restore the animation when back to visible
        console.log("visible is true, enable the animation")
        selectedBall.selected = true
    }
}
